import weakref

__doc__=\
"""
From
	j knapka, WeakMethod, http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81253
	
	Reference to a bound method that permits the associated object
	to be garbage collected.

	A normal bound method "hides" a strong reference to the bound
	method's object. That means that the object can't be
	garbage-collected until the bound method is disposed of...
	
	Normal weakref.refs to bound methods don't quite work the way one expects, 
	because bound methods are first-class objects; weakrefs to bound
	methods are dead-on-arrival unless some other strong reference
	to the same bound method exists.
"""

class weak_method_wrap:
	def __init__(me, f):
		assert f is not None, "not sure can do weak references to None"
		me.is_bound=hasattr(f,'im_func')
		if me.is_bound:
			me.weakref    = weakref.ref(f.im_self)
			me.class_meth = f.im_func
		else:
			me.weakref    = weakref.ref(f)
	def __call__(me, *args, **kw):
		if me.weakref()==None:
			if me.is_bound: msg='Method called on dead object'
			else:           msg='Function no longer exists'
			raise ReferenceError(msg)
			
		if me.is_bound:
			class_instance=me.weakref()
			me.class_meth(class_instance,*args,**kw)
		else:
			f=me.weakref()
			f(*args,**kw)
	def is_dead(me): return me.weakref()==None
	def __cmp__(me,other):
		if me.is_bound <> other.is_bound:
			return 1
		
		if me.is_bound:
			if me.weakref()<>other.weakref(): return 1
			return cmp(me.class_meth,other.class_meth)

		return cmp(me.weakref(),other.weakref())
	
def sanity_test():
	"""
	>>> import gc
	
	Unbound function?
	>>> def f():print "f"
	...
	>>> wr=weak_method_wrap(f)
	>>> wr()
	f
	>>> wr.is_dead()
	False
	>>> del f
	>>> gc.collect()
	0
	>>> wr.is_dead()
	True
	>>> wr()
	Traceback (most recent call last):
	ReferenceError: Function no longer exists
	
	Bound class method?
	>>> class C:
	...	def m(me):print "m"
	...
	>>> c=C()
	>>> wr=weak_method_wrap(c.m)
	>>> wr()
	m
	>>> wr.is_dead()
	False
	>>> del c
	>>> gc.collect()
	0
	>>> wr.is_dead()
	True
	>>> wr()
	Traceback (most recent call last):
	ReferenceError: Method called on dead object
	"""

def test_class_meth_compare():
	"""
	>>> class K:
	... 	def meth1():pass
	... 	def meth2():pass
	...
	>>> k=K()
	>>> w1=weak_method_wrap(k.meth1)
	>>> w2=weak_method_wrap(k.meth2)
	>>> assert(w1<>w2)
	"""				
if __name__=="__main__":
	import doctest
	doctest.testmod()